chore: dedupe retry/auth step helpers and small cleanups in http/pipeline/steps#196
Open
OmarAlJarrah wants to merge 4 commits into
Open
chore: dedupe retry/auth step helpers and small cleanups in http/pipeline/steps#196OmarAlJarrah wants to merge 4 commits into
OmarAlJarrah wants to merge 4 commits into
Conversation
DefaultAsyncInstrumentationStep.processAsync initialised `mdc` with an
MdcSnapshot.capture() that is overwritten on the first line inside the
trace-context use{} block, so every async request paid for one MDC copy
that was immediately discarded. Use `lateinit var` instead: the real
capture still happens inside the scope (after trace.id / span.id are
pushed), the post-scope read is only reachable once the block has run, and
the wasted snapshot is gone.
handleChallenge ran the challenge future through a handle that only packed
its (value, error) into a one-field HookOutcome, then a thenCompose that
unpacked it to do the real work. Do the close / fail / dispatch decision
inside a single handle that returns the next CompletableFuture, and flatten
it with thenCompose { it }. This removes the carrier type and a whole
composition stage; the explicit null-check smart-casts the retry request
for the dispatch. Behaviour is identical across all three completion paths
(no retry, retry, hook error).
BearerTokenAuthStep and AsyncBearerTokenAuthStep each defined a private offersBearerChallenge(response) and a private "bearer" scheme constant with identical bodies. Hoist both to a single top-level internal function and constant in BearerTokenAuthStep.kt (mirroring how defaultShouldRetryResponse already lives top-level next to HttpRetryOptions) and call it from both steps. The async file drops its copy and its now-orphaned AuthChallengeParser import. evictRejectedToken stays per-step — it touches each step's own lock and cached token.
DefaultRetryStep and DefaultAsyncRetryStep carried byte-for-byte copies of the entire stateless retry policy: options clamping, the RetrySettings backoff view, re-sendability gating, interrupt normalisation, predicate invocation, the caller delay override, and fixed/exponential backoff. Only the per-call loop machinery and terminal-failure paths genuinely differ between the blocking and async stacks. Pull the stateless policy into one internal RetryPolicySupport that clamps the options and builds the backoff settings once and exposes the shared helpers; both steps now hold a `support` and delegate. The retry decision and backoff logic lives in one place, so a change to clamping, backoff, or predicate-exception handling can no longer apply to only one stack. Each step keeps what actually differs: the per-call should-retry checks (they read tryCount / suppressed), the distinct terminal paths, the protected-open delay hooks, and closeQuietly with its deliberately different catch width (sync swallows IOException, async swallows Exception). RetryPolicySupport takes only (rawOptions, logger): the clock is unused by any helper that moved — retryAfterFromHeaders, the only clock consumer, stays on each step. Also add an internal HttpRetryOptions.withMaxRetries copy helper so the negative-maxRetries clamp no longer hand-rebuilds all eight fields through the constructor; RetryPolicySupport.clampOptions collapses to a single line. All internal — no public signature changes, apiCheck unaffected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Several behavior-preserving cleanups in
sdk-core'shttp/pipeline/steps, where the sync/async step pairs had drifted into duplicated helper blocks. All changes are internal — no public signatures move, soapiCheckis unaffected — and land as separate commits.Changes
Extract
RetryPolicySupport(the structural one).DefaultRetryStepandDefaultAsyncRetryStepcarried byte-for-byte copies of the entire stateless retry policy: options clamping, theRetrySettingsbackoff view, re-sendability gating, interrupt normalisation, predicate invocation, the caller delay override, and fixed/exponential backoff. Only the per-call loop machinery and terminal-failure paths genuinely differ. The policy now lives in oneinternal class RetryPolicySupport; both steps hold asupportand delegate. Each step keeps what actually differs — the per-call should-retry checks (which readtryCount/suppressed), the distinct terminal paths, theprotected opendelay hooks, andcloseQuietlywith its deliberately different catch width (sync swallowsIOException, async swallowsException).RetryPolicySupporttakes(rawOptions, logger)— notclock: none of the helpers that moved into it use the clock, and the only clock consumer (retryAfterFromHeaders) deliberately stays on each step. Aclockfield there would be dead and trip detekt.Add
HttpRetryOptions.withMaxRetries.HttpRetryOptionsisn't adata class, so the negative-maxRetriesclamp hand-rebuilt all eight fields through the constructor. A singleinternalcopy helper lives on the type that owns the fields, andRetryPolicySupport.clampOptionscollapses to one line. (A targeted helper beats converting todata class, which would pushcopy/componentNinto the public API.)Hoist
offersBearerChallenge. Both bearer steps defined an identical privateoffersBearerChallenge+"bearer"constant. They move to one top-levelinternalfunction and constant inBearerTokenAuthStep.kt(mirroring the existing top-leveldefaultShouldRetryResponse); the async file drops its copy and its now-orphanedAuthChallengeParserimport.evictRejectedTokenstays per-step (it touches each step's own lock/cache).Fold the
AsyncAuthStepchallenge carrier.handleChallengeran the challenge future through ahandlethat only boxed(value, error)into a one-fieldHookOutcome, then athenComposethat unboxed it to do the work. A singlehandle { … }.thenCompose { it }does the close/fail/dispatch decision directly, removing the carrier type and a composition stage. Behaviour is identical across all three completion paths (no-retry, retry, hook error).Drop the throwaway MDC capture.
DefaultAsyncInstrumentationStepinitialisedmdcwith anMdcSnapshot.capture()that was overwritten on the first line inside the trace-context scope — one wasted snapshot per async request.lateinit varexpresses the intent (assigned inside the scope, read only after it runs) without the wasted capture.Verification
Locally on
sdk-core: fulltestsuite,compileKotlin(underallWarningsAsErrors),ktlint,detekt, andapiCheckall pass. I traced theAsyncAuthStepfold across normal/retry/exceptional challenge completions and confirmed the retry-policy helpers are byte-identical between the two stacks before extraction.Closes #169